package com.whz.governance.redislimiter.filter;

import com.google.common.collect.Lists;
import com.whz.generic.api.ApiResponseWrapper;
import com.whz.generic.api.ApiStatus;
import com.whz.generic.json.JacksonUtils;
import com.whz.governance.redislimiter.config.RedisRateLimiterProperties;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.lang.NonNull;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * 基于redis的remoteIp限流过滤器
 *
 * @author whz
 */
@Slf4j
public class RedisRateLimitFilter extends OncePerRequestFilter {

    private final CompositeRedisRateLimitSupport support;

    public RedisRateLimitFilter(final RedisRateLimiterProperties redisRateLimiterProperties) {
        List<RedisRateLimitSupporter> limitSupporters = Lists.newLinkedList();
        redisRateLimiterProperties.getConfig().forEach((k, v) -> {
            limitSupporters.add(new RedisRateLimitSupporter(k, v, redisRateLimiterProperties.getWhiteIp()));
        });

        support = new CompositeRedisRateLimitSupport(limitSupporters);
    }

    @Override
    protected void doFilterInternal(@NonNull final HttpServletRequest request,
                                    @NonNull final HttpServletResponse response,
                                    @NonNull final FilterChain filterChain) throws ServletException, IOException {

        try {
            String requestUri = request.getRequestURI();
            if (support.match(requestUri)) {
                if (!support.allowAccess(request)) {
                    log.info("RequestURI:{} match rate limiter.", requestUri);
                    reject(response);
                    return;
                }
            }
        } catch (Exception e) {
            log.error("Rate Limiter Filter Error.", e);
        }

        /*
         * allowed, go on
         */
        filterChain.doFilter(request, response);
    }

    @SneakyThrows
    private static void reject(HttpServletResponse response) {
        ApiResponseWrapper<String> apiTongyuResponse = new ApiResponseWrapper<>();
        apiTongyuResponse.setStatus(ApiStatus.FAILURE);
        apiTongyuResponse.setCode(HttpStatus.TOO_MANY_REQUESTS.value());
        apiTongyuResponse.setError("Too Many Requests,please try it again later.");
        apiTongyuResponse.fromService();
        response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
        JacksonUtils.writeToOutput(response.getOutputStream(), apiTongyuResponse);
    }
}